package com.xhwl.data.service.impl;


import com.xhwl.common.constant.cent.message.MessageConstant;
import com.xhwl.common.enums.CodeEnum;
import com.xhwl.common.enums.config.IndustryEnum;
import com.xhwl.common.exception.BusinessException;
import com.xhwl.common.exception.DataException;
import com.xhwl.common.pojo.Config;
import com.xhwl.common.pojo.ResultJson;
import com.xhwl.common.pojo.cent.VerificationCode;
import com.xhwl.common.pojo.cent.sdata.Account;
import com.xhwl.common.pojo.msg.NoticeData;
import com.xhwl.common.pojo.msg.Target;
import com.xhwl.common.utils.CentDataVerifyUtil;
import com.xhwl.common.utils.DateUtils;
import com.xhwl.data.service.*;
import com.xhwl.data.util.RedisUtils;
import com.xhwl.starter.config.IConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.security.SecureRandom;
import java.util.concurrent.ThreadLocalRandom;


/**
 * @Author JCccc
 * @Description
 * @Date 2021/8/31 10:57
 */
@Service
@Slf4j
public class MessageCodeServiceImpl implements IMessageCodeService {
    @Autowired
    RedisUtils redisUtils;
    @Autowired
    IMessageCentService messageCentService;
    @Autowired
    IProjectService projectService;
    @Autowired
    private IConfig config;
    @Autowired
    IAccountService accountService;

    private String EMAIL_TEMPLATE_CODE = "email_default";

    @Override
    public ResultJson sendMsgCodeOpen(String phone, Integer projectId, String industryId) {
        if (null == industryId) {
            return ResultJson.fail("缺少业态信息");
        }
        //手机号校验
        CentDataVerifyUtil.phoneVerify(phone);
        //短信验证规则校验
        ResultJson sendRuleVerifyResult = msgSendRuleVerify(phone, industryId);
        if (!CodeEnum.SUCCESS.getCode().equals(sendRuleVerifyResult.getCode())) {
            return sendRuleVerifyResult;
        }
        //获取短信模板id
        Integer templateId = getMsgTemplateId(projectId, industryId, null);
        //生成验证码
        String msgCode = createMsgCode(MessageConstant.MSG_CODE_LENGTH);
        //发送短信验证码
        msgCenterSend(phone, msgCode, templateId);
        //把相关操作存入redis
        saveMsgCodeCache(phone, industryId, msgCode);
        return ResultJson.success("短信验证码已发送，请查收");
    }

    @Override
    public ResultJson sendEmailCode(String email, Integer projectId, String industryId) {
        if (null == industryId) {
            return ResultJson.fail("缺少业态信息");
        }
        CentDataVerifyUtil.emailVerify(email);
        //短信验证规则校验
        ResultJson sendRuleVerifyResult = emailSendRuleVerify(email, industryId);
        if (!CodeEnum.SUCCESS.getCode().equals(sendRuleVerifyResult.getCode())) {
            return sendRuleVerifyResult;
        }
        //生成验证码
        String msgCode = createMsgCode(MessageConstant.MSG_CODE_LENGTH);
        //通过模板编码发送邮箱验证码
        msgCenterSend(email,msgCode,EMAIL_TEMPLATE_CODE);
        //把相关操作存入redis
        saveMsgCodeCache(email, industryId, msgCode);
        return ResultJson.success("邮箱验证码已发送，请查收");
    }

    /**
     * 发送短信
     *
     * @param phone
     * @param projectId
     * @param operateAccount
     * @return
     */
    @Override
    public ResultJson sendMsgCode(String phone, Integer projectId, Account operateAccount) {
        if (null == operateAccount.getIndustryId()) {
            return ResultJson.fail("缺少业态信息");
        }
        Short industryId = operateAccount.getIndustryId();
        //手机号校验
        CentDataVerifyUtil.phoneVerify(phone);
        //短信验证规则校验
        ResultJson sendRuleVerifyResult = msgSendRuleVerify(phone, String.valueOf(industryId));
        if (!CodeEnum.SUCCESS.getCode().equals(sendRuleVerifyResult.getCode())) {
            return sendRuleVerifyResult;
        }
        //获取短信模板id
        Integer templateId = getMsgTemplateId(projectId, String.valueOf(industryId), null);
        //生成验证码
        String msgCode = createMsgCode(MessageConstant.MSG_CODE_LENGTH);
        //发送短信验证码
        msgCenterSend(phone, msgCode, templateId);
        //把相关操作存入redis
        saveMsgCodeCache(phone, String.valueOf(industryId  ), msgCode);
        return ResultJson.success("短信验证码已发送，请查收");
    }


    /**
     * 发送短信规则校验 （频繁发送 & 账号锁定）
     *
     * @param phone
     * @param industryId
     * @return
     */
    private ResultJson msgSendRuleVerify(String phone, String industryId) {

        //检测是否已经触发账户锁定，如果触发，不能进行校验&发送
        if (null != redisUtils.get(industryId + "_" + MessageConstant.MSG_CODE_SEND_LOCKING + phone)) {
            return ResultJson.fail(CodeEnum.MSG_CODE_LOCKING);
        }
        //校验是否能发送，60秒内不能重复发送
        if (null != redisUtils.get(industryId + "_" + MessageConstant.MSG_CODE_SEND_LIMIT + phone)) {
            return ResultJson.fail(CodeEnum.MSG_CODE_FREQUENT);
        }
        return ResultJson.success();
    }

    /**
     * 发送短信规则校验 （频繁发送 & 账号锁定）
     *
     * @param email
     * @param industryId
     * @return
     */
    private ResultJson emailSendRuleVerify(String email, String industryId) {

        //检测是否已经触发账户锁定，如果触发，不能进行校验&发送
        if (null != redisUtils.get(industryId + "_" + MessageConstant.MSG_CODE_SEND_LOCKING + email)) {
            return ResultJson.fail(CodeEnum.MSG_CODE_LOCKING);
        }
        //校验是否能发送，60秒内不能重复发送
        if (null != redisUtils.get(industryId + "_" + MessageConstant.MSG_CODE_SEND_LIMIT + email)) {
            return ResultJson.fail(CodeEnum.EMAIL_CODE_FREQUENT);
        }
        return ResultJson.success();
    }
    /**
     * 生成短信验证码
     *
     * @return
     */
    public String createMsgCode(Integer msgCodeLength) {
        return String.valueOf(ThreadLocalRandom.current().nextInt(100000, 1000000));
    }

    /**
     * 获取模板
     *
     * @param projectId
     * @param industryId
     * @return
     */
    public Integer getMsgTemplateId(Integer projectId, String industryId, String templateCode) {
        Integer templateId;
        //  todo 短信模板动态获取
        if (StringUtils.isEmpty(templateCode)) {
            if (industryId.equals(String.valueOf(IndustryEnum.SCH.getId()))) {
                templateCode = MessageConstant.SCH_MSG_CODE;
            } else if (industryId.equals(String.valueOf(IndustryEnum.RSD.getId()))) {
                templateCode = MessageConstant.RSD_MSG_CODE;
            } else if (industryId.equals(String.valueOf(IndustryEnum.BUZ.getId()))) {
                templateCode = MessageConstant.BUZ_MSG_CODE;
            } else if (industryId.equals(String.valueOf(IndustryEnum.PRD.getId()))) {
                templateCode = MessageConstant.PRD_MSG_CODE;
            } else {
                templateCode = "default_msg_template";
            }
        }
        if (null == projectId) {
            Config industryConfig = config.getIndustry(templateCode, Integer.valueOf(industryId));
            if(null == industryConfig){
                throw new DataException("配置不存在");
            }
            templateId = Integer.valueOf(String.valueOf(industryConfig.getValue()));
        } else {
            Config projectConfig = config.getProject(templateCode, projectId);
            if(null == projectConfig){
                log.error("发送短信获取模版配置 {} 失败", templateCode);
                throw new DataException("获取模版配置失败");
            }
            templateId = Integer.valueOf(String.valueOf(projectConfig.getValue()));
        }
        log.info("正在使用templateCode={} ，获取出来的消息模板id={}", templateCode, templateId);
        return templateId;
    }


    /**
     * 存入短信验证码以及一些校验数据
     *
     * @param phone
     * @param industryId
     * @param msgCode
     */
    public void saveMsgCodeCache(String phone, String industryId, String msgCode) {
        //能否发送前提 范围 1分钟
        redisUtils.setWithMinute(industryId + "_" + MessageConstant.MSG_CODE_SEND_LIMIT + phone, msgCode, MessageConstant.MSG_CODE_SEND_LIMIT_NUMBER_TIMES);
        //有效时间暂定5分钟
        redisUtils.setWithMinute(industryId + "_" + MessageConstant.MSG_CODE_PREFIX + phone, msgCode, MessageConstant.MSG_CODE_VALID_MIN);
    }

    /**
     * 调用消息中心发送短信
     *
     * @param phone
     * @param msgCode
     * @param templateId
     */
    public void msgCenterSend(String phone, String msgCode, Integer templateId) {
        ResultJson sendResult;
        NoticeData noticeData = new NoticeData();
        noticeData.setTemplateId(templateId);
        noticeData.setContent("验证码是" + msgCode + "(" + MessageConstant.MSG_CODE_VALID_MIN + "分钟内有效)。");
        Target target = new Target();
        target.setReceivers(Arrays.asList(phone));
        sendResult = messageCentService.send(templateId, noticeData, target);
        log.info("消息中台发送短信结果={}", sendResult.getMessage());
        if (!CodeEnum.SUCCESS.getCode().equals(sendResult.getCode())) {
            throw new BusinessException("短信发送失败");
        }

    }


    /**
     * 调用消息中心发送短信
     *
     * @param email
     * @param msgCode
     * @param templateCode
     */
    public void msgCenterSend(String email, String msgCode, String templateCode) {
        ResultJson sendResult;
        NoticeData noticeData = new NoticeData();
        noticeData.setContent("验证码是" + msgCode + "(" + MessageConstant.MSG_CODE_VALID_MIN + "分钟内有效)。");
        noticeData.setTitle("验证码通知");
        Target target = new Target();
        target.setReceivers(Arrays.asList(email));
        sendResult = messageCentService.send(templateCode,noticeData,target);
        log.info("消息中台发送短信结果={}", sendResult.getMessage());
        if (!CodeEnum.SUCCESS.getCode().equals(sendResult.getCode())) {
            throw new BusinessException(sendResult.getMessage());
        }

    }
    /**
     * 手机 短信验证码 校验
     *
     * @param phone
     * @param msgCode
     * @return
     */
    @Override
    public ResultJson phoneMsgCodeVerify(String industryId, String phone, String msgCode, Boolean needRemove) {

        if (null == needRemove) {
            //默认设置校验完需要移除 ，如果不需要移除则是过程中多次校验场景
            needRemove = true;
        }
        if (null == industryId) {
            return ResultJson.fail("缺少业态信息");
        }
        CentDataVerifyUtil.msgCodeVerify(msgCode);
        if (null == redisUtils.get(industryId + "_" + MessageConstant.MSG_CODE_PREFIX + phone)) {
            int errorCount = 1;
            Long secondsToBeforeDawn = DateUtils.getSecondsToBeforeDawn();
            if (null == redisUtils.get(industryId + "_" + MessageConstant.MSG_CODE_ERROR_COUNT + phone)) {

                //设置当前自然日内错误次数 为 1
                redisUtils.setWithSeconds(industryId + "_" + MessageConstant.MSG_CODE_ERROR_COUNT + phone, errorCount, secondsToBeforeDawn);
            } else {
                //设置当前自然日内错误次数自增
                redisUtils.increment(industryId + "_" + MessageConstant.MSG_CODE_ERROR_COUNT + phone);
                errorCount = (Integer) redisUtils.get(industryId + "_" + MessageConstant.MSG_CODE_ERROR_COUNT + phone);
                //重新赋值，不改变验证码有效时间

                if (null==redisUtils.get(industryId + "_" + MessageConstant.MSG_CODE_PREFIX + phone)){
                    redisUtils.setWithMinute(industryId + "_" + MessageConstant.MSG_CODE_PREFIX + phone, "", MessageConstant.MSG_CODE_VALID_MIN);
                }else {
                    String msgCodeCache = String.valueOf(redisUtils.get(industryId + "_" + MessageConstant.MSG_CODE_PREFIX + phone));
                    redisUtils.setWithSeconds(industryId + "_" + MessageConstant.MSG_CODE_PREFIX + phone, msgCodeCache,redisUtils.getExpire(industryId + "_" + MessageConstant.MSG_CODE_PREFIX + phone));
                }

                //区分场景提示语，到达5次，触发账户锁定；不达5次，返回常规错误校验提示
                if (MessageConstant.MSG_CODE_SEND_LOCKING_NUMBER_TIMES <= errorCount) {
                    //触发账号锁定触发
                    String msgCodeCache = String.valueOf(redisUtils.get(industryId + "_" + MessageConstant.MSG_CODE_PREFIX + phone));
                    redisUtils.setWithMinute(industryId + "_" + MessageConstant.MSG_CODE_SEND_LOCKING + phone, msgCodeCache, 10);
                    return ResultJson.fail(CodeEnum.MSG_CODE_LOCKING);
                }

            }
            return ResultJson.fail(CodeEnum.MSG_CODE_WRONG);
        }
        //检测是否已经触发账户锁定，如果触发，不能进行校验
        if (null != redisUtils.get(industryId + "_" + MessageConstant.MSG_CODE_SEND_LOCKING + phone)) {
            return ResultJson.fail(CodeEnum.MSG_CODE_LOCKING);
        }
        String msgCodeCache = String.valueOf(redisUtils.get(industryId + "_" + MessageConstant.MSG_CODE_PREFIX + phone));
        //校验 验证码是否正确
        if (!msgCodeCache.equals(msgCode)) {
            //验证码错误处理 （计数）
            return errorMsgCodeDeal(industryId, phone, msgCodeCache);
        }
        if (needRemove) {
            redisUtils.remove(industryId + "_" + MessageConstant.MSG_CODE_PREFIX + phone);
            redisUtils.remove(industryId + "_" + MessageConstant.MSG_CODE_SEND_LIMIT + phone);
        }
        return ResultJson.success("验证校验通过");
    }

    @Override
    public ResultJson sendCode(VerificationCode verificationCode) {
        return null;
    }

    /**
     * 短信验证码 触发错误计数
     *
     * @param industryId
     * @param phone
     * @param msgCodeCache
     * @return
     */
    private ResultJson errorMsgCodeDeal(String industryId, String phone, String msgCodeCache) {
        int errorCount = 1;
        Long secondsToBeforeDawn = DateUtils.getSecondsToBeforeDawn();
        if (null == redisUtils.get(industryId + "_" + MessageConstant.MSG_CODE_ERROR_COUNT + phone)) {
            //设置当前自然日内错误次数 为 1
            redisUtils.setWithSeconds(industryId + "_" + MessageConstant.MSG_CODE_ERROR_COUNT + phone, errorCount, secondsToBeforeDawn);
        } else {
            //设置当前自然日内错误次数自增
            redisUtils.increment(industryId + "_" + MessageConstant.MSG_CODE_ERROR_COUNT + phone);
        }

        errorCount = (Integer) redisUtils.get(industryId + "_" + MessageConstant.MSG_CODE_ERROR_COUNT + phone);
        //重新赋值，不改变验证码有效时间
        redisUtils.setWithSeconds(industryId + "_" + MessageConstant.MSG_CODE_PREFIX + phone, msgCodeCache, redisUtils.getExpire(industryId + "_" + MessageConstant.MSG_CODE_PREFIX + phone));
        //区分场景提示语，到达5次，触发账户锁定；不达5次，返回常规错误校验提示
        if (MessageConstant.MSG_CODE_SEND_LOCKING_NUMBER_TIMES <= errorCount) {
            //触发账号锁定触发
            redisUtils.setWithMinute(industryId + "_" + MessageConstant.MSG_CODE_SEND_LOCKING + phone, msgCodeCache, 10);
            return ResultJson.fail(CodeEnum.MSG_CODE_LOCKING);
        }
        return ResultJson.fail(CodeEnum.MSG_CODE_WRONG);
    }

}
